package net.lb.socketclient.listen;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import net.lb.baseutil.collection.ByteBufferPool;
import net.lb.baseutil.collection.ByteCache;
import net.lb.socketclient.ClientUtil;
import net.lb.socketclient.filter.DataFilter;
import net.lb.socketclient.filter.EndecrptFilter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class ClientChannelInfo
{
    private ClientUtil                         clientUtil;
    private long                               readTimeout         = 3000;
    private long                               lastReadTime        = 0;
    private long                               endReadTime         = 0;
    private volatile boolean                   startCountdown      = false;
    private long                               reuseChannelTimeout = 60000;
    private long                               lastUseChannelTime  = 0;
    private volatile AsynchronousSocketChannel socketChannel       = null;
    private String                             address;
    protected final Queue<ReadResult>          queue               = new ConcurrentLinkedQueue<>();
    private ClientReadHandler                  readCompHandler;
    private final Logger                       logger              = LogManager.getLogger();
    private ByteBuffer                         readBuffer          = ByteBufferPool.getBuffer(1024);
    private Lock                               lock;
    private Condition                          finishRead;
    private GetReadResult                      getReadResult;
    private DataFilter[]                       dataFilters         = new DataFilter[0];
    private EndecrptFilter                     endecrptFilter;
    private volatile Boolean                   closed              = Boolean.FALSE;
    
    public ClientChannelInfo(ClientReadHandler readHandler)
    {
        this.readCompHandler = readHandler;
    }
    
    /**
     * 在通道上继续读取未读取完整的数据
     */
    public void continueRead()
    {
        if (startCountdown == false)
        {
            lastReadTime = System.currentTimeMillis();
            endReadTime = lastReadTime + readTimeout;
            startCountdown = true;
        }
        socketChannel.read(readBuffer, getRemainTime(), TimeUnit.MILLISECONDS, this, readCompHandler);
        lastReadTime = System.currentTimeMillis();
    }
    
    /**
     * 剩余的读取消息时间
     * 
     * @return
     */
    public long getRemainTime()
    {
        return endReadTime - lastReadTime;
    }
    
    /**
     * 在通道上进行空闲等待监听，监听的超时时间为通道重用超时时间.并且重置读取倒计时为false
     */
    public void startReadWait()
    {
        startCountdown = false;
        socketChannel.read(readBuffer, reuseChannelTimeout, TimeUnit.MILLISECONDS, this, readCompHandler);
    }
    
    /**
     * 将通道上的读取结果推送至通道的等待队列中
     * 
     * @param readResult
     */
    public void offerReadResult(ReadResult readResult)
    {
        queue.offer(readResult);
    }
    
    /**
     * 通道是否已经过期
     * 
     * @return
     */
    public boolean expired()
    {
        if (System.currentTimeMillis() - lastUseChannelTime > reuseChannelTimeout)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    
    /**
     * 关闭通道对象的socket
     */
    public void closeSocket()
    {
        try
        {
            //下次判断是否可以重用时直接返回超时
            lastUseChannelTime = 0;
            socketChannel.close();
        }
        catch (IOException e)
        {
            logger.error("关闭通道{}出现异常", address, e);
        }
    }
    
    /**
     * 关闭通道当前socket，释放readBuffer资源
     */
    protected void close()
    {
        if (closed)
        {
            return;
        }
        synchronized (closed)
        {
            if (closed == false)
            {
                closed = true;
            }
            else
            {
                return;
            }
        }
        try
        {
            socketChannel.close();
        }
        catch (IOException e)
        {
            logger.error("关闭通道{}出现异常", address, e);
        }
        ByteBufferPool.returnBuffer(readBuffer);
        readBuffer = null;
    }
    
    public void handleResult(ByteCache cache, byte command, byte result)
    {
        if (endecrptFilter != null)
        {
            endecrptFilter.inFilter(cache);
        }
        for (DataFilter each : dataFilters)
        {
            each.inFilter(cache);
        }
        ReadResult readResult = queue.poll();
        if (readResult != null)
        {
            Object InvokeResult;
            try
            {
                InvokeResult = getReadResult.getResult(cache, command, result);
                readResult.setResult(InvokeResult);
            }
            catch (Throwable e1)
            {
                readResult.setReadException(e1);
            }
            // 必须放在这个地方，由于条件存在虚假唤醒的可能，如果在前面一开始就设置为true。如果被虚假唤醒了，则readResult会返回null
            readResult.setFinishStatus(true);
            lock.lock();
            try
            {
                finishRead.signalAll();
            }
            finally
            {
                lock.unlock();
            }
        }
        else
        {
            throw new RuntimeException("当前通道内的消息数量" + queue.size());
        }
    }
    
    /**
     * socket通道发生异常时会被调用的方法
     * 通道发生了异常，为了维持数据的完整性，只能关闭通道。故而需要取出该通道上的所有读等待结果，进行设置
     * 
     * @param e
     */
    public void handleException(Throwable e)
    {
        getReadResult.handlerException(e, this);
        ReadResult readResult;
        while (true)
        {
            readResult = queue.poll();
            if (readResult != null)
            {
                readResult.setReadException(e);
                // 由于存在虚假唤醒，所以设置完成这种变量都需要在其他元素都设置完毕的时候才可以
                readResult.setFinishStatus(true);
            }
            else
            {
                break;
            }
        }
        lock.lock();
        try
        {
            finishRead.signalAll();
        }
        finally
        {
            lock.unlock();
        }
    }
    
    public long getReadTimeout()
    {
        return readTimeout;
    }
    
    public void setReadTimeout(long readTimeout)
    {
        this.readTimeout = readTimeout;
    }
    
    public long getReuseChannelTimeout()
    {
        return reuseChannelTimeout;
    }
    
    public void setReuseChannelTimeout(long reuseChannelTimeout)
    {
        this.reuseChannelTimeout = reuseChannelTimeout;
    }
    
    public AsynchronousSocketChannel getSocketChannel()
    {
        return socketChannel;
    }
    
    public void setSocketChannel(AsynchronousSocketChannel socketChannel)
    {
        this.socketChannel = socketChannel;
    }
    
    public String getAddress()
    {
        return address;
    }
    
    public void setAddress(String address)
    {
        this.address = address;
    }
    
    public void setLastUseChannelTime()
    {
        lastUseChannelTime = System.currentTimeMillis();
    }
    
    public ClientUtil getClientUtil()
    {
        return clientUtil;
    }
    
    public void setClientUtil(ClientUtil clientUtil)
    {
        this.clientUtil = clientUtil;
    }
    
    public void setLock(Lock lock)
    {
        this.lock = lock;
    }
    
    public void setFinishRead(Condition finishRead)
    {
        this.finishRead = finishRead;
    }
    
    public void setGetReadResult(GetReadResult getReadResult)
    {
        this.getReadResult = getReadResult;
    }
    
    public ByteBuffer getReadBuffer()
    {
        return readBuffer;
    }
    
    public void setDataFilters(DataFilter[] dataFilters)
    {
        this.dataFilters = dataFilters;
    }
    
    public void setEndecrptFilter(EndecrptFilter endecrptFilter)
    {
        this.endecrptFilter = endecrptFilter;
    }
    
    public Queue<ReadResult> getQueue()
    {
        return queue;
    }
    
    public void setReadBuffer(ByteBuffer readBuffer)
    {
        this.readBuffer = readBuffer;
    }
    
}
